---------------------
-- Robotic Panel   --
---------------------
-- A Cirr Creation --
---------------------
-- This file includes the basic movement functions which
-- define how a panel moves, and are usually associated with
-- one or more behaviours.
Panels.Movement = {}

-- HOVER -------------------------------------------------
-- Calculations for hovering, self-righting and bobbing --
function Panels.Movement.Hover(panel, phys, delta)

  if(!panel:IsValid()) then
    return
  end
  
  -- SELF-RIGHT
  
  local targAng
  
  targAng = phys:GetAngles()
  targAng.p = 90
  targAng.y = 0
  
  -- HOVER OFF GROUND
  -- Get the z-position whatever surface we're above through a traceline
  local currPos = panel:GetPos()
  local endPos = Vector(currPos.x, currPos.y, currPos.z - (Panels.HOVER_HEIGHT * 2))
  
  local tr = util.TraceLine(
  {
    start = currPos,
    endpos = endPos,
    filter = panel,
    mask = MASK_SOLID | MASK_WATER
  })
  
  local targPos = currPos
  
  if(!tr.Hit) then -- If we're so high the trace didn't hit
  
    targPos.z = targPos.z - Panels.HOVER_HEIGHT * 2 -- drop down the entire length of the trace line
    
  end
  
  -- Otherwise we hit a position.
  
  targPos.z = tr.HitPos.z + Panels.HOVER_HEIGHT + (math.sin(panel.HoverPhase) * Panels.HOVER_DISPLACE)
  
  return targPos, targAng
  
end

-- APPROACH ------------------------------------------------
-- Approach the target, while navigating around obstacles --
function Panels.Movement.Approach(panel, phys, delta)
  
  if(!panel:IsValid()) then
    return
  end
  
  local targPos, targAng = Panels.Movement.Hover(panel, phys, delta)
  
  if(!panel.Target:IsValid()) then
    
    panel.MovementFunction = Panels.Movement.Hover
    return targPos, targAng
    
  end
  
  -- FACE TOWARDS TARGET
  -- Adjust targAngle to face target
  local currPos = panel:GetPos()
  local targetPos = panel.Target:GetPos()
  local newAng = (targetPos - currPos):Angle()
  targAng.r = -newAng.y
  
  -- ADJUST DIRECTION
  -- Check to see the way forwards is clear
  local dir = targAng:Up()
  local dist = currPos:Distance(targetPos)
  local diff = dist - panel.TargetDistance
  local endPos = currPos + (dir * math.Min(Panels.PROPEL_SPEED, diff))
  
  if(diff > 0) then
  
    local tr = util.TraceLine(
    {
      start = currPos,
      endpos = endPos,
      filter = panel,
      mask = MASK_SOLID | MASK_WATER
    })
    
    if(tr.Hit) then -- We need to steer! STEER! OH GOD!
      
      local hit = true
      local offset = panel:WorldToLocal(targetPos)
      local angDir
      if(offset.y > 0) then
        angDir = 1
      else
        angDir = -1
      end
      
      local startAngle = targAng.r -- To avoid infinite loops
      
      while(hit) do -- While we return a hit
        
        targAng.r = targAng.r + (angDir * Panels.TURN_STEP)
        
        dir = targAng:Up()
        endPos = currPos + (dir * math.Min(Panels.PROPEL_SPEED, diff))
        tr = util.TraceLine(
        {
          start = currPos,
          endpos = endPos,
          filter = panel,
          mask = MASK_SOLID | MASK_WATER
        })
        
        hit = tr.Hit
        
        if(math.abs(targAng.r - startAngle) > 360) then -- We've checked a full circle, there's no way of moving
        
          return targPos, targAng
          
        end
        
      end
      
      targPos = targPos + (dir * -1 * Panels.PROPEL_SPEED) -- Drop back to turn!
      return targPos, targAng -- Don't move, we're going to steer
      
    else
    
      targPos = targPos + (dir * math.Min(Panels.PROPEL_SPEED, diff))
      
    end
  
  else
  
    targPos = targPos + (dir * math.Min(Panels.PROPEL_SPEED, math.abs(diff)) * -1)
    
  end
  
  return targPos, targAng

end

-- FLEE ------------------------------------------------
-- Flee from the target, while running from obstacles --
function Panels.Movement.Flee(panel, phys, delta)
  
  if(!panel:IsValid()) then
    return
  end
  
  local targPos, targAng = Panels.Movement.Hover(panel, phys, delta)
  
  if(!panel.Target:IsValid()) then
    
    panel.MovementFunction = Panels.Movement.Hover
    return targPos, targAng
    
  end
  
  -- FACE AWAY FROM TARGET
  -- Adjust targAngle to face away from target
  local currPos = panel:GetPos()
  local targetPos = panel.Target:GetPos()
  local newAng = (targetPos - currPos):Angle()
  targAng.r = newAng.y
  
  -- ADJUST DIRECTION
  -- Check to see the way forwards is clear
  local dir = targAng:Up()
  local endPos = currPos + (dir * Panels.PROPEL_SPEED * 2)
  
  local tr = util.TraceLine(
  {
    start = currPos,
    endpos = endPos,
    filter = panel,
    mask = MASK_SOLID | MASK_WATER
  })
  
  if(tr.Hit) then -- We need to steer! STEER! OH GOD!
      
    local hit = true
    local offset = panel:WorldToLocal(targetPos)
    local angDir
    if(offset.y > 0) then
      angDir = 1
    else
      angDir = -1
    end
    
    local startAngle = targAng.r -- To avoid infinite loops
    
    while(hit) do -- While we return a hit
      
      targAng.r = targAng.r + (angDir * Panels.TURN_STEP)
      
      dir = targAng:Up()
      endPos = currPos + (dir * Panels.PROPEL_SPEED * 2)
      tr = util.TraceLine(
      {
        start = currPos,
        endpos = endPos,
        filter = panel,
        mask = MASK_SOLID | MASK_WATER
      })
      
      hit = tr.Hit
      
      if(math.abs(targAng.r - startAngle) > 360) then -- We've checked a full circle, there's no way of moving
        
        return targPos, targAng
          
      end
      
    end
    
    targPos = targPos + (dir * -1 * Panels.PROPEL_SPEED) -- Drop back to turn!
    return targPos, targAng -- Don't move, we're going to steer
    
  end
  
  -- MOVE AWAY FROM TARGET
  -- Otherwise the path is clear and we move away!!
  targPos = targPos + (dir * Panels.PROPEL_SPEED * 2)
  
  return targPos, targAng

end